package idx

import (
	"bytes"
	"fmt"
	"github.com/wuzfei/go-helper/slices"
	"github.com/zeebo/errs"
	"io"
	"log"
	"os"
	"sync"
	"sync/atomic"
	"time"
)

const FileMod = 0744

var ErrFileStore = errs.Class("FileStore")

type FileStore struct {
	mux      sync.Mutex
	fileName string
	fd       *FileDat

	writeStop bool //禁止写入
	readStop  bool //禁止读取
	cond      *sync.Cond

	writeRef int32
	readRef  int32
}

// Close 关闭文件
func (f *FileStore) Close() (err error) {
	return f.fd.Close()
}

// NewFileStore 打开一个新的文件存储
func NewFileStore(fileName string) (*FileStore, error) {
	fd, err := openIdxDat(fileName)
	if err != nil {
		return nil, ErrFileStore.Wrap(err)
	}
	return &FileStore{
		fileName: fileName,
		cond:     sync.NewCond(&sync.Mutex{}),
		fd:       fd,
	}, nil
}

// PutFrom 从io.Reader读取保存
func (f *FileStore) PutFrom(key string, r io.Reader, size int64, expireTime time.Time) (md5 []byte, err error) {
	md5, err = f.put(key, r, size, expireTime)
	if err != nil {
		err = ErrFileStore.New("[PutFrom]: %s", err)
	}
	return
}

// PutBytes 从[]byte读取保存
func (f *FileStore) PutBytes(key string, data []byte, expireTime time.Time) (md5 []byte, err error) {
	l := int64(len(data))
	if l == 0 {
		return nil, ErrFileStore.New("data is empty")
	}
	md5, err = f.put(key, bytes.NewReader(data), l, expireTime)
	if err != nil {
		err = ErrFileStore.New("[PutBytes]: %s", err)
	}
	return
}

// PutFile 从 file 读取保存
func (f *FileStore) PutFile(key string, fileName string, expireTime time.Time) (md5 []byte, err error) {
	var fp *os.File
	var size int64
	fp, size, err = GetFile(fileName)
	if err != nil {
		return
	}
	if key == "" {
		key = fp.Name()
	}
	md5, err = f.put(key, fp, size, expireTime)
	if err != nil {
		err = ErrFileStore.New("[PutFile]: %s", err)
	}
	return
}

func (f *FileStore) put(key string, r io.Reader, size int64, expireTime time.Time) (md5 []byte, err error) {
	for f.writeStop {
		f.cond.L.Lock()
		f.cond.Wait()
		f.cond.L.Unlock()
	}

	f.WriteIncr(1)
	defer f.WriteIncr(-1)

	fdw := f.fd.NewDatWriter(key, size, expireTime)
	defer fdw.Close()
	//写入空的数据的时候，不要copy，copy不会执行write方法，直接执行写入idx就行了
	if size == 0 {
		err = fdw.WriteIdx()
		if err != nil {
			return nil, fmt.Errorf("file put write idx err: %s", err)
		}
	} else {
		var n int64
		n, err = io.Copy(fdw, r)
		if err != nil {
			return nil, fmt.Errorf("file put copy err: %s", err)
		}
		if n != size {
			err = fmt.Errorf("写入错误, 实际写入：%d, 指定大小：%d, err: %s", n, size, err)
			return
		}
	}
	return fdw.Md5(), nil
}

// GetTo 根据key读取，返回, md5, size,err
func (f *FileStore) GetTo(key string, w io.Writer) (md5 []byte, size int64, err error) {
	var r io.ReadCloser
	r, md5, size, err = f.get(key)
	if err != nil {
		return
	}
	defer r.Close()
	var n int64
	n, err = io.Copy(w, r)
	if n != size {
		err = ErrFileStore.New("[GetReader]: 读取数据异常，存储：%d, 读取：%d", size, n)
	}
	if err != nil {
		err = ErrFileStore.New("[GetReader]: %s", err)
	}
	return
}

// GetBytes 根据key读取，返回[]byte, md5, size,err
func (f *FileStore) GetBytes(key string) (b []byte, md5 []byte, size int64, err error) {
	var r io.ReadCloser
	r, md5, size, err = f.get(key)
	if err != nil {
		err = ErrFileStore.New("[GetBytes]: %s", err)
		return
	}
	defer r.Close()
	buf := bytes.NewBuffer(nil)
	_, err = io.Copy(buf, r)
	if err != nil {
		err = ErrFileStore.New("file get copy err: %s", err)
		return
	}
	b = buf.Bytes()
	return
}

// GetReader 根据key读取，返回[]byte, md5, size,err
func (f *FileStore) GetReader(key string) (r io.ReadCloser, md5 []byte, size int64, err error) {
	return f.get(key)
}

// get 返回io.Reader
func (f *FileStore) get(key string) (r io.ReadCloser, md5 []byte, size int64, err error) {
	for f.readStop {
		f.cond.L.Lock()
		f.cond.Wait()
		f.cond.L.Unlock()
	}

	f.ReadIncr(1)
	defer f.ReadIncr(-1)

	idx := f.fd.GetIdx(key)
	if idx == nil {
		err = fmt.Errorf("key[%s] no exists", key)
		return
	}
	r = f.fd.NewDatReader(idx.Offset, idx.Size)
	md5 = idx.Md5
	size = idx.Size
	return
}

// Delete 根据key读取，返回[]byte, md5, size,err
func (f *FileStore) Delete(key string) (err error) {
	return f.fd.Delete(key)
}

func (f *FileStore) CleanUp() error {
	fs, err := NewFileStore(cleanFile(f.fileName))
	if err != nil {
		return err
	}
	//获取当前清理的key, 锁定当前key

	keys := f.fd.LockIdxMaps()
	f.cleanupCopy(keys, fs)

	//剩余的key处理，不允许写了
	f.writeStop = true
	for {
		if f.WriteIncr(0) == 0 && f.fd.writeRefIncr(0) == 0 {
			break
		}
	}
	f.cleanupCopy(f.fd.UnLockIdxMaps(), fs)
	err = fs.Close()
	if err != nil {
		f.writeStop = false
		f.cond.Broadcast()
		return fmt.Errorf("关闭复制对象失败：%s", err)
	}

	//更新文件，不允许读了
	f.readStop = true
	for {
		if f.ReadIncr(0) == 0 && f.fd.readRefIncr(0) == 0 {
			break
		}
	}
	eg := errs.Group{}
	eg.Add(f.fd.Close(), os.Rename(datFile(cleanFile(f.fileName)), datFile(f.fileName)))
	if eg.Err() != nil {
		f.readStop = false
		f.writeStop = false
		f.cond.Broadcast()
		return fmt.Errorf("复制对象文件失败：%s", eg.Err())
	}

	//重置fd 对象
	fd, err := openIdxDat(f.fileName)
	if err != nil {
		return fmt.Errorf("重新打开文件对象失败：%s", err)
	}
	f.fd = fd

	f.writeStop = false
	f.readStop = false
	f.cond.Broadcast()
	return nil
}

func (f *FileStore) cleanupCopy(keys []string, fs *FileStore) {
	for _, k := range keys {
		v := f.fd.GetIdx(k)
		if v == nil {
			continue
		}
		if v.Expired() {
			log.Printf("key[%s]已过期：%s", k, time.Unix(v.Expire, 0))
			continue
		}
		if v.Del() {
			log.Printf("key[%s]已删除", k)
			continue
		}
		r := f.fd.NewDatReader(v.Offset, v.Size)
		md5, _err := fs.PutFrom(k, r, v.Size, time.Unix(v.Expire, 0))
		r.Close()
		if _err != nil {
			log.Printf("key[%s]复制失败:%s", k, _err)
			continue
		}
		if !slices.Equal(md5, v.Md5) {
			log.Printf("key[%s]复制后md5不对，旧：%x, 新：%x", k, v.Md5, md5)
			continue
		}
	}
}

func (f *FileStore) WriteIncr(n int32) int32 {
	if n == 0 {
		return atomic.LoadInt32(&f.writeRef)
	}
	return atomic.AddInt32(&f.writeRef, n)
}

func (f *FileStore) ReadIncr(n int32) int32 {
	if n == 0 {
		return atomic.LoadInt32(&f.readRef)
	}
	return atomic.AddInt32(&f.readRef, n)
}

func (f *FileStore) Idle() bool {
	return atomic.LoadInt32(&f.writeRef)+atomic.LoadInt32(&f.readRef) <= 0 && f.fd.writeRefIncr(0)+f.fd.readRefIncr(0) <= 0
}
